/**
 * @fileoverview Provides compatibility shims so that legacy JavaScript
 * engines behave as closely as possible to ES5.
 *
 * @see :
 * - http://es5.github.com/
 * - http://kangax.github.com/es5-compat-table/
 * - https://github.com/kriskowal/es5-shim
 * - http://perfectionkills.com/extending-built-in-native-objects-evil-or-not/
 * - https://gist.github.com/1120592
 * - https://code.google.com/p/v8/
 */

(function() {

	var OP = Object.prototype,
		AP = Array.prototype,
		FP = Function.prototype,
		SP = String.prototype,
		hasOwnProperty = OP.hasOwnProperty,
		slice = AP.slice;

	// ES-5 15.3.4.5
	// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
	FP.bind || (FP.bind = function (that) {
		var target = this;
	
		// If IsCallable(func) is false, throw a TypeError exception.
		if (typeof target !== 'function') {
		throw new TypeError('Bind must be called on a function');
		}
	
		var boundArgs = slice.call(arguments, 1);
	
		function bound() {
		// Called as a constructor.
		if (this instanceof bound) {
			var self = createObject(target.prototype);
			var result = target.apply(
				self,
				boundArgs.concat(slice.call(arguments))
			);
			return Object(result) === result ? result : self;
		}
		// Called as a function.
		else {
			return target.apply(
				that,
				boundArgs.concat(slice.call(arguments))
			);
		}
		}

		// NOTICE: The function.length is not writable.
		//bound.length = Math.max(target.length - boundArgs.length, 0);

		return bound;
	});


	// Helpers
	function createObject(proto) {
		var o;
	
		if (Object.create) {
			o = Object.create(proto);
		}
		else {
			function F() {
		}
	
		F.prototype = proto;
			o = new F();
		}
	
		return o;
	}

	// http://ejohn.org/blog/ecmascript-5-objects-and-properties/
	
	// ES5 15.2.3.14
	// http://whattheheadsaid.com/2010/10/a-safer-object-keys-compatibility-implementation
	// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/keys
	// https://developer.mozilla.org/en/ECMAScript_DontEnum_attribute
	// http://msdn.microsoft.com/en-us/library/adebfyya(v=vs.94).aspx
	Object.keys || (Object.keys = (function () {
		var hasDontEnumBug = !{toString:''}.propertyIsEnumerable('toString');
		var DontEnums = [
		'toString',
		'toLocaleString',
		'valueOf',
		'hasOwnProperty',
		'isPrototypeOf',
		'propertyIsEnumerable',
		'constructor'
		];
		var DontEnumsLength = DontEnums.length;
	
		return function (o) {
		if (o !== Object(o)) {
			throw new TypeError(o + ' is not an object');
		}
	
		var result = [];
	
		for (var name in o) {
			if (hasOwnProperty.call(o, name)) {
			result.push(name);
			}
		}
	
		if (hasDontEnumBug) {
			for (var i = 0; i < DontEnumsLength; i++) {
			if (hasOwnProperty.call(o, DontEnums[i])) {
				result.push(DontEnums[i]);
			}
			}
		}
	
		return result;
		};
	
	})());


	// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array
	// https://github.com/kangax/fabric.js/blob/gh-pages/src/util/lang_array.js
	
	// ES5 15.4.3.2
	// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/isArray
	Array.isArray || (Array.isArray = function (obj) {
		return OP.toString.call(obj) === '[object Array]';
	});
	
	
	// ES5 15.4.4.18
	// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/foreach
	AP.forEach || (AP.forEach = function (fn, context) {
		for (var i = 0, len = this.length >>> 0; i < len; i++) {
		if (i in this) {
			fn.call(context, this[i], i, this);
		}
		}
	});
	
	
	// ES5 15.4.4.19
	// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/map
	AP.map || (AP.map = function (fn, context) {
		var len = this.length >>> 0;
		var result = new Array(len);
	
		for (var i = 0; i < len; i++) {
		if (i in this) {
			result[i] = fn.call(context, this[i], i, this);
		}
		}
	
		return result;
	});
	
	
	// ES5 15.4.4.20
	// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/filter
	AP.filter || (AP.filter = function (fn, context) {
		var result = [], val;
	
		for (var i = 0, len = this.length >>> 0; i < len; i++) {
		if (i in this) {
			val = this[i]; // in case fn mutates this
			if (fn.call(context, val, i, this)) {
			result.push(val);
			}
		}
		}
	
		return result;
	});
	
	
	// ES5 15.4.4.16
	// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/every
	AP.every || (AP.every = function (fn, context) {
		for (var i = 0, len = this.length >>> 0; i < len; i++) {
		if (i in this && !fn.call(context, this[i], i, this)) {
			return false;
		}
		}
		return true;
	});
	
	
	// ES5 15.4.4.17
	// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/some
	AP.some || (AP.some = function (fn, context) {
		for (var i = 0, len = this.length >>> 0; i < len; i++) {
		if (i in this && fn.call(context, this[i], i, this)) {
			return true;
		}
		}
		return false;
	});
	
	
	// ES5 15.4.4.21
	// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduce
	AP.reduce || (AP.reduce = function (fn /*, initial*/) {
		if(typeof fn !== 'function') {
		throw new TypeError(fn + ' is not an function');
		}
	
		var len = this.length >>> 0, i = 0, result;
	
		if (arguments.length > 1) {
		result = arguments[1];
		}
		else {
		do {
			if (i in this) {
			result = this[i++];
			break;
			}
			// if array contains no values, no initial value to return
			if (++i >= len) {
			throw new TypeError('reduce of empty array with on initial value');
			}
		}
		while (true);
		}
	
		for (; i < len; i++) {
		if (i in this) {
			result = fn.call(null, result, this[i], i, this);
		}
		}
	
		return result;
	});
	
	
	// ES5 15.4.4.22
	// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduceRight
	AP.reduceRight || (AP.reduceRight = function (fn /*, initial*/) {
		if(typeof fn !== 'function') {
		throw new TypeError(fn + ' is not an function');
		}
	
		var len = this.length >>> 0, i = len - 1, result;
	
		if (arguments.length > 1) {
		result = arguments[1];
		}
		else {
		do {
			if (i in this) {
			result = this[i--];
			break;
			}
			// if array contains no values, no initial value to return
			if (--i < 0)
			throw new TypeError('reduce of empty array with on initial value');
		}
		while (true);
		}
	
		for (; i >= 0; i--) {
		if (i in this) {
			result = fn.call(null, result, this[i], i, this);
		}
		}
	
		return result;
	});
	
	
	// ES5 15.4.4.14
	// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/indexOf
	AP.indexOf || (AP.indexOf = function (value, from) {
		var len = this.length >>> 0;
	
		from = Number(from) || 0;
		from = Math[from < 0 ? 'ceil' : 'floor'](from);
		if (from < 0) {
		from = Math.max(from + len, 0);
		}
	
		for (; from < len; from++) {
		if (from in this && this[from] === value) {
			return from;
		}
		}
	
		return -1;
	});
	
	
	// ES5 15.4.4.15
	// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/indexOf
	AP.lastIndexOf || (AP.lastIndexOf = function (value, from) {
		var len = this.length >>> 0;
	
		from = Number(from) || len - 1;
		from = Math[from < 0 ? 'ceil' : 'floor'](from);
		if (from < 0) {
		from += len;
		}
		from = Math.min(from, len - 1);
	
		for (; from >= 0; from--) {
		if (from in this && this[from] === value) {
			return from;
		}
		}
	
		return -1;
	});

	// ES5 15.5.4.20
	// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/trim
	// http://blog.stevenlevithan.com/archives/faster-trim-javascript
	// http://jsperf.com/mega-trim-test
	SP.trim || (SP.trim = (function () {
	
		// http://perfectionkills.com/whitespace-deviations/
		var whiteSpaces = [
	
		'\\s',
		//'0009', // 'HORIZONTAL TAB'
		//'000A', // 'LINE FEED OR NEW LINE'
		//'000B', // 'VERTICAL TAB'
		//'000C', // 'FORM FEED'
		//'000D', // 'CARRIAGE RETURN'
		//'0020', // 'SPACE'
	
		'00A0', // 'NO-BREAK SPACE'
		'1680', // 'OGHAM SPACE MARK'
		'180E', // 'MONGOLIAN VOWEL SEPARATOR'
	
		'2000-\\u200A',
		//'2000', // 'EN QUAD'
		//'2001', // 'EM QUAD'
		//'2002', // 'EN SPACE'
		//'2003', // 'EM SPACE'
		//'2004', // 'THREE-PER-EM SPACE'
		//'2005', // 'FOUR-PER-EM SPACE'
		//'2006', // 'SIX-PER-EM SPACE'
		//'2007', // 'FIGURE SPACE'
		//'2008', // 'PUNCTUATION SPACE'
		//'2009', // 'THIN SPACE'
		//'200A', // 'HAIR SPACE'
	
		'200B', // 'ZERO WIDTH SPACE (category Cf)
		'2028', // 'LINE SEPARATOR'
		'2029', // 'PARAGRAPH SEPARATOR'
		'202F', // 'NARROW NO-BREAK SPACE'
		'205F', // 'MEDIUM MATHEMATICAL SPACE'
		'3000' //'IDEOGRAPHIC SPACE'
	
		].join('\\u');
	
		var trimLeftReg = new RegExp('^[' + whiteSpaces + ']+');
		var trimRightReg = new RegExp('[' + whiteSpaces + ']+$');
	
		return function() {
		return String(this).replace(trimLeftReg, '').replace(trimRightReg, '');
		}
	
	})());


	// ES5 15.9.4.4
	// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Date/now
	Date.now || (Date.now = function () {
		return +new Date;
	});

})();
